{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 机器学习工程师纳米学位\n",
    "## 监督学习\n",
    "## 项目 2: 搭建一个学生干预系统"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "欢迎来到机器学习工程师纳米学位的第二个项目！在此文件中，有些示例代码已经提供给你，但你还需要实现更多的功能让项目成功运行。除非有明确要求，你无须修改任何已给出的代码。以**'练习'**开始的标题表示接下来的代码部分中有你必须要实现的功能。每一部分都会有详细的指导，需要实现的部分也会在注释中以**'TODO'**标出。请仔细阅读所有的提示！\n",
    "\n",
    "除了实现代码外，你还**必须**回答一些与项目和你的实现有关的问题。每一个需要你回答的问题都会以**'问题 X'**为标题。请仔细阅读每个问题，并且在问题后的**'回答'**文字框中写出完整的答案。我们将根据你对问题的回答和撰写代码所实现的功能来对你提交的项目进行评分。\n",
    "\n",
    ">**提示：**Code 和 Markdown 区域可通过 **Shift + Enter** 快捷键运行。此外，Markdown可以通过双击进入编辑模式。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 问题 1 - 分类 vs. 回归\n",
    "*在这个项目中你的任务是找出那些如果不给予帮助，最重可能无法毕业的学生。你觉得这个问题是哪种类型的监督学习问题，是分类问题还是回归问题？为什么？*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**答案: **"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 分析数据\n",
    "运行下面区域的代码以载入学生数据集，以及一些此项目所需的Python库。注意数据集的最后一列`'passed'`是我们的预测的目标（表示学生是毕业了还是没有毕业），其他的列是每个学生的属性。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 载入所需要的库\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from time import time\n",
    "from sklearn.metrics import f1_score\n",
    "\n",
    "# 载入学生数据集\n",
    "student_data = pd.read_csv(\"student-data.csv\")\n",
    "print \"Student data read successfully!\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 练习: 分析数据\n",
    "我们首先通过调查数据，以确定有多少学生的信息，并了解这些学生的毕业率。在下面的代码单元中，你需要完成如下的运算：\n",
    "- 学生的总数， `n_students`。\n",
    "- 每个学生的特征总数， `n_features`。\n",
    "- 毕业的学生的数量， `n_passed`。\n",
    "- 未毕业的学生的数量， `n_failed`。\n",
    "- 班级的毕业率， `grad_rate`， 用百分数表示(%)。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# TODO： 计算学生的数量\n",
    "n_students = None\n",
    "\n",
    "# TODO： 计算特征数量\n",
    "n_features = None\n",
    "\n",
    "# TODO： 计算通过的学生数\n",
    "n_passed = None\n",
    "\n",
    "# TODO： 计算未通过的学生数\n",
    "n_failed = None\n",
    "\n",
    "# TODO： 计算通过率\n",
    "grad_rate = None\n",
    "\n",
    "# 输出结果\n",
    "print \"Total number of students: {}\".format(n_students)\n",
    "print \"Number of features: {}\".format(n_features)\n",
    "print \"Number of students who passed: {}\".format(n_passed)\n",
    "print \"Number of students who failed: {}\".format(n_failed)\n",
    "print \"Graduation rate of the class: {:.2f}%\".format(grad_rate)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据准备\n",
    "在这个部分中，我们将要为建模、训练和测试准备数据\n",
    "### 识别特征和目标列\n",
    "你获取的数据中通常都会包含一些非数字的特征，这会导致一些问题，因为大多数的机器学习算法都会期望输入数字特征进行计算。\n",
    "\n",
    "运行下面的代码单元将学生数据分成特征和目标列看一看他们中是否有非数字特征。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 提取特征列\n",
    "feature_cols = list(student_data.columns[:-1])\n",
    "\n",
    "# 提取目标列 ‘passed’\n",
    "target_col = student_data.columns[-1] \n",
    "\n",
    "# 显示列的列表\n",
    "print \"Feature columns:\\n{}\".format(feature_cols)\n",
    "print \"\\nTarget column: {}\".format(target_col)\n",
    "\n",
    "# 将数据分割成特征数据和目标数据（即X_all 和 y_all）\n",
    "X_all = student_data[feature_cols]\n",
    "y_all = student_data[target_col]\n",
    "\n",
    "# 通过打印前5行显示特征信息\n",
    "print \"\\nFeature values:\"\n",
    "print X_all.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 预处理特征列\n",
    "\n",
    "正如你所见，我们这里有几个非数值的列需要做一定的转换！它们中很多是简单的`yes`/`no`，比如`internet`。这些可以合理地转化为`1`/`0`（二元值，binary）值。\n",
    "\n",
    "其他的列，如`Mjob`和`Fjob`，有两个以上的值，被称为_分类变量（categorical variables）_。处理这样的列的推荐方法是创建和可能值一样多的列（如：`Fjob_teacher`，`Fjob_other`，`Fjob_services`等），然后将其中一个的值设为`1`另外的设为`0`。\n",
    "\n",
    "这些创建的列有时候叫做 _虚拟变量（dummy variables）_，我们将用[`pandas.get_dummies()`](http://pandas.pydata.org/pandas-docs/stable/generated/pandas.get_dummies.html?highlight=get_dummies#pandas.get_dummies)函数来完成这个转换。运行下面代码单元的代码来完成这里讨论的预处理步骤。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def preprocess_features(X):\n",
    "    ''' 预处理学生数据，将非数字的二元特征转化成二元值（0或1），将分类的变量转换成虚拟变量\n",
    "    '''\n",
    "    \n",
    "    # 初始化一个用于输出的DataFrame\n",
    "    output = pd.DataFrame(index = X.index)\n",
    "\n",
    "    # 查看数据的每一个特征列\n",
    "    for col, col_data in X.iteritems():\n",
    "        \n",
    "        # 如果数据是非数字类型，将所有的yes/no替换成1/0\n",
    "        if col_data.dtype == object:\n",
    "            col_data = col_data.replace(['yes', 'no'], [1, 0])\n",
    "\n",
    "        # 如果数据类型是类别的（categorical），将它转换成虚拟变量\n",
    "        if col_data.dtype == object:\n",
    "            # 例子: 'school' => 'school_GP' and 'school_MS'\n",
    "            col_data = pd.get_dummies(col_data, prefix = col)  \n",
    "        \n",
    "        # 收集转换后的列\n",
    "        output = output.join(col_data)\n",
    "    \n",
    "    return output\n",
    "\n",
    "X_all = preprocess_features(X_all)\n",
    "print \"Processed feature columns ({} total features):\\n{}\".format(len(X_all.columns), list(X_all.columns))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 实现: 将数据分成训练集和测试集\n",
    "现在我们已经将所有的 _分类的（categorical）_ 特征转换成数值了。下一步我们将把数据（包括特征和对应的标签数据）分割成训练集和测试集。在下面的代码单元中，你需要完成下列功能：\n",
    "- 随机混洗（shuffle）切分数据(`X_all`, `y_all`) 为训练子集和测试子集。\n",
    "  - 使用300个数据点作为训练集（约75%），使用95个数据点作为测试集（约25%）。\n",
    "  - 如果可能的话，为你使用的函数设置一个`random_state`。\n",
    "  - 将结果存储在`X_train`, `X_test`, `y_train`和 `y_test`中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# TODO：在这里导入你可能需要使用的另外的功能\n",
    "\n",
    "# TODO：设置训练集的数量\n",
    "num_train = None\n",
    "\n",
    "# TODO：设置测试集的数量\n",
    "num_test = X_all.shape[0] - num_train\n",
    "\n",
    "# TODO：把数据集混洗和分割成上面定义的训练集和测试集\n",
    "X_train = None\n",
    "X_test = None\n",
    "y_train = None\n",
    "y_test = None\n",
    "\n",
    "# 显示分割的结果\n",
    "print \"Training set has {} samples.\".format(X_train.shape[0])\n",
    "print \"Testing set has {} samples.\".format(X_test.shape[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练和评价模型\n",
    "在这个部分，你将选择3个适合这个问题并且在`scikit-learn`中已有的监督学习的模型。首先你需要说明你选择这三个模型的原因，包括这些数据集有哪些特点，每个模型的优点和缺点各是什么。然后，你需要将这些模型用不同大小的训练集（100个数据点，200个数据点，300个数据点）进行训练，并用F<sub>1</sub>的值来衡量。你需要制作三个表，每个表要显示训练集大小，训练时间，预测时间，训练集上的F<sub>1</sub>值和测试集上的F<sub>1</sub>值（每个模型一个表）。\n",
    "\n",
    "**这是目前** [`scikit-learn`](http://scikit-learn.org/stable/supervised_learning.html) **里有的监督学习模型，你可以从中选择:**\n",
    "- Gaussian Naive Bayes (GaussianNB) 朴素贝叶斯\n",
    "- Decision Trees 决策树\n",
    "- Ensemble Methods (Bagging, AdaBoost, Random Forest, Gradient Boosting)\n",
    "- K-Nearest Neighbors (KNeighbors)\n",
    "- Stochastic Gradient Descent (SGDC)\n",
    "- Support Vector Machines (SVM) 向量模型机\n",
    "- Logistic Regression 逻辑回归"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 问题 2 - 应用模型\n",
    "*列出三个适合这个问题的监督学习算法模型。每一个你选择的模型：*\n",
    "\n",
    "- 描述一个该模型在真实世界的一个应用场景。（你需要为此做点研究，并给出你的引用出处）\n",
    "- 这个模型的优势是什么？他什么情况下表现最好？\n",
    "- 这个模型的缺点是什么？什么条件下它表现很差？\n",
    "- 根据我们当前数据集的特点，为什么这个模型适合这个问题。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**回答: **"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 准备\n",
    "运行下面的代码单元以初始化三个帮助函数，这三个函数将能够帮你训练和测试你上面所选择的三个监督学习算法。这些函数是：\n",
    "- `train_classifier` - 输入一个分类器和训练集，用数据来训练这个分类器。\n",
    "- `predict_labels` - 输入一个训练好的分类器、特征以及一个目标标签，这个函数将帮你做预测并给出F<sub>1</sub>的值.\n",
    "- `train_predict` - 输入一个分类器以及训练集和测试集，它可以运行`train_clasifier`和`predict_labels`.\n",
    " - 这个函数将分别输出训练集的F<sub>1</sub>值和测试集的F<sub>1</sub>值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def train_classifier(clf, X_train, y_train):\n",
    "    ''' 用训练集训练分类器 '''\n",
    "    \n",
    "    # 开始计时，训练分类器，然后停止计时\n",
    "    start = time()\n",
    "    clf.fit(X_train, y_train)\n",
    "    end = time()\n",
    "    \n",
    "    # Print the results\n",
    "    print \"Trained model in {:.4f} seconds\".format(end - start)\n",
    "\n",
    "    \n",
    "def predict_labels(clf, features, target):\n",
    "    ''' 用训练好的分类器做预测并输出F1值'''\n",
    "    \n",
    "    # 开始计时，作出预测，然后停止计时\n",
    "    start = time()\n",
    "    y_pred = clf.predict(features)\n",
    "    end = time()\n",
    "    \n",
    "    # 输出并返回结果\n",
    "    print \"Made predictions in {:.4f} seconds.\".format(end - start)\n",
    "    return f1_score(target.values, y_pred, pos_label='yes')\n",
    "\n",
    "\n",
    "def train_predict(clf, X_train, y_train, X_test, y_test):\n",
    "    ''' 用一个分类器训练和预测，并输出F1值 '''\n",
    "    \n",
    "    # 输出分类器名称和训练集大小\n",
    "    print \"Training a {} using a training set size of {}. . .\".format(clf.__class__.__name__, len(X_train))\n",
    "    \n",
    "    # 训练一个分类器\n",
    "    train_classifier(clf, X_train, y_train)\n",
    "    \n",
    "    # 输出训练和测试的预测结果\n",
    "    print \"F1 score for training set: {:.4f}.\".format(predict_labels(clf, X_train, y_train))\n",
    "    print \"F1 score for test set: {:.4f}.\".format(predict_labels(clf, X_test, y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 练习: 模型评价指标\n",
    "借助于上面定义的函数，你现在需要导入三个你选择的监督学习模型，然后为每一个模型运行`train_predict`函数。请记住，对于每一个模型你需要在不同大小的训练集（100，200和300）上进行训练和测试。所以，你在下面应该会有9个不同的输出（每个模型都有训练集大小不同的三个输出）。在接下来的代码单元中，你将需要实现以下功能：\n",
    "- 引入三个你在上面讨论过的监督式学习算法模型。\n",
    "- 初始化三个模型并将它们存储在`clf_A`， `clf_B` 和 `clf_C`中。\n",
    " - 如果可能对每一个模型都设置一个`random_state`。\n",
    " - **注意:** 这里先使用每一个模型的默认参数，在接下来的部分中你将需要对某一个模型的参数进行调整。\n",
    "- 创建不同大小的训练集用来训练每一个模型。\n",
    " - *不要再混洗和再分割数据！新的训练集要取自`X_train`和`y_train`.*\n",
    "- 对于每一个模型要用不同大小的训练集来训练它，然后在测试集上做测试（总共需要9次训练测试）   \n",
    "**注意:** 在下面的代码单元后面我们提供了三个表用来存储你的结果。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# TODO：从sklearn中引入三个监督学习模型\n",
    "# from sklearn import model_A\n",
    "# from sklearn import model_B\n",
    "# from skearln import model_C\n",
    "\n",
    "# TODO：初始化三个模型\n",
    "clf_A = None\n",
    "clf_B = None\n",
    "clf_C = None\n",
    "\n",
    "# TODO：设置训练集大小\n",
    "X_train_100 = None\n",
    "y_train_100 = None\n",
    "\n",
    "X_train_200 = None\n",
    "y_train_200 = None\n",
    "\n",
    "X_train_300 = None\n",
    "y_train_300 = None\n",
    "\n",
    "# TODO：对每一个分类器和每一个训练集大小运行'train_predict' \n",
    "# train_predict(clf, X_train, y_train, X_test, y_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 结果表格\n",
    "编辑下面的表格看看在[Markdown](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#tables)中如何设计一个表格。你需要把上面的结果记录在表格中。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "** 分类器 1 - ?**  \n",
    "\n",
    "| 训练集大小 | 训练时间 | 预测时间 (测试) | F1值 (训练) | F1值 (测试) |\n",
    "| :---------------: | :---------------------: | :--------------------: | :--------------: | :-------------: |\n",
    "| 100               |                         |                        |                  |                 |\n",
    "| 200               |        EXAMPLE          |                        |                  |                 |\n",
    "| 300               |                         |                        |                  |    EXAMPLE      |\n",
    "\n",
    "** 分类器 2 - ?**  \n",
    "\n",
    "| 训练集大小 | 训练时间 | 预测时间 (测试) | F1值 (训练) | F1值 (测试) |\n",
    "| :---------------: | :---------------------: | :--------------------: | :--------------: | :-------------: |\n",
    "| 100               |                         |                        |                  |                 |\n",
    "| 200               |        EXAMPLE          |                        |                  |                 |\n",
    "| 300               |                         |                        |                  |    EXAMPLE      |\n",
    "\n",
    "** 分类器 3 - ?**  \n",
    "\n",
    "| 训练集大小 | 训练时间 | 预测时间 (测试) | F1值 (训练) | F1值 (测试) |\n",
    "| :---------------: | :---------------------: | :--------------------: | :--------------: | :-------------: |\n",
    "| 100               |                         |                        |                  |                 |\n",
    "| 200               |        EXAMPLE          |                        |                  |                 |\n",
    "| 300               |                         |                        |                  |    EXAMPLE      |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 选择最佳模型\n",
    "在最后这一部分中，你将从三个监督学习模型中选择一个用在学生数据上的最佳模型。然后你将在最佳模型上用全部的训练集（`X_train`和`y_train`）运行一个网格搜索算法，在这个过程中，你要至少调整一个参数以提高模型的F<sub>1</sub>值（相比于没有调参的模型的分值有所提高）。 "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 问题 3 - 选择最佳模型\n",
    "*给予你上面做的实验，用一到两段话，向（学校）监事会解释你将选择哪个模型作为最佳的模型。哪个模型在现有的数据，有限的资源、开支和模型表现综合来看是最好的选择？*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**回答: **"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 问题 4 - 用通俗的语言解释模型\n",
    "*用一到两段话，向（学校）监事会用外行也听得懂的话来解释最终模型是如何工作的。你需要解释所选模型的主要特点。例如，这个模型是怎样被训练的，它又是如何做出预测的。避免使用高级的数学或技术术语，不要使用公式或特定的算法名词。*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**回答: **"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 练习: 模型调参\n",
    "细调选择的模型的参数。使用网格搜索（`GridSearchCV`）来至少调整模型的重要参数（至少调整一个），这个参数至少需给出并尝试3个不同的值。你要使用整个训练集来完成这个过程。在接下来的代码单元中，你需要实现以下功能：\n",
    "- 导入 [`sklearn.grid_search.gridSearchCV`](http://scikit-learn.org/stable/modules/generated/sklearn.grid_search.GridSearchCV.html) 和 [`sklearn.metrics.make_scorer`](http://scikit-learn.org/stable/modules/generated/sklearn.metrics.make_scorer.html).\n",
    "- 创建一个对于这个模型你希望调整参数的字典。\n",
    " - 例如: `parameters = {'parameter' : [list of values]}`。\n",
    "- 初始化你选择的分类器，并将其存储在`clf`中。\n",
    "- 使用`make_scorer` 创建F<sub>1</sub>评分函数并将其存储在`f1_scorer`中。\n",
    " - 需正确设定参数`pos_label`的值！\n",
    "- 在分类器`clf`上用`f1_scorer` 作为评价函数运行网格搜索,并将结果存储在`grid_obj`中。\n",
    "- 用训练集(`X_train`, `y_train`)训练grid search object,并将结果存储在`grid_obj`中。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# TODO: 导入 'GridSearchCV' 和 'make_scorer'\n",
    "\n",
    "# TODO：创建你希望调整的参数列表\n",
    "parameters = None\n",
    "\n",
    "# TODO：初始化分类器\n",
    "clf = None\n",
    "\n",
    "# TODO：用'make_scorer'创建一个f1评分函数\n",
    "f1_scorer = None\n",
    "\n",
    "# TODO：在分类器上使用f1_scorer作为评分函数运行网格搜索\n",
    "grid_obj = None\n",
    "\n",
    "# TODO: Fit the grid search object to the training data and find the optimal parameters\n",
    "# TODO：用训练集训练grid search object来寻找最佳参数\n",
    "grid_obj = None\n",
    "\n",
    "# Get the estimator\n",
    "# 得到预测的结果\n",
    "clf = grid_obj.best_estimator_\n",
    "\n",
    "# Report the final F1 score for training and testing after parameter tuning\n",
    "# 输出经过调参之后的训练集和测试集的F1值\n",
    "print \"Tuned model has a training F1 score of {:.4f}.\".format(predict_labels(clf, X_train, y_train))\n",
    "print \"Tuned model has a testing F1 score of {:.4f}.\".format(predict_labels(clf, X_test, y_test))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 问题 5 - 最终的 F<sub>1</sub> 值\n",
    "*最终模型的训练和测试的F<sub>1</sub>值是多少？这个值相比于没有调整过参数的模型怎么样？*"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**回答: **"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **注意**: 当你写完了所有的代码，并且回答了所有的问题。你就可以把你的 iPython Notebook 导出成 HTML 文件。你可以在菜单栏，这样导出**File -> Download as -> HTML (.html)**把这个 HTML 和这个 iPython notebook 一起做为你的作业提交。  "
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 2",
   "language": "python",
   "name": "python2"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
